package handler

import (
	"context"
	"io"
	"net"
	"strings"
	"sync"

	"goRedis/cluster"
	"goRedis/config"
	"goRedis/database"
	databaseface "goRedis/interface/database"
	"goRedis/lib/logger"
	"goRedis/lib/sync/atomic"
	"goRedis/resp/connection"
	"goRedis/resp/parser"
	"goRedis/resp/reply"
)

var (
	unknownErrReplyBytes = []byte("-ERR unknown\r\n")
)

// RespHandler implements tcp.Handler and serves as a redis handler
type RespHandler struct {
	activeConn sync.Map // *client -> placeholder
	db         databaseface.Database
	closing    atomic.Boolean // refusing new client and new request
}

// MakeHandler creates a RespHandler instance
func MakeHandler() *RespHandler {
	var db databaseface.Database
	//db = database.NewEchoDatabase()
	if config.Properties.Self != "" &&
		len(config.Properties.Peers) > 0 {
		db = cluster.MakeClusterDatabase()
	} else {
		db = database.NewStandaloneDatabase()
	}
	return &RespHandler{
		db: db,
	}
}

func (r *RespHandler) closeClient(client *connection.Connection) {
	_ = client.Close()
	r.db.AfterClientClose(client)
	r.activeConn.Delete(client)
}

// Handle receives and executes redis commands
func (r *RespHandler) Handle(ctx context.Context, conn net.Conn) {
	if r.closing.Get() {
		// closing handler refuse new connection
		_ = conn.Close()
	}

	client := connection.NewConn(conn)
	r.activeConn.Store(client, struct{}{})

	ch := parser.ParseStream(conn)
	for payload := range ch {
		if payload.Err != nil {
			if payload.Err == io.EOF ||
				payload.Err == io.ErrUnexpectedEOF ||
				strings.Contains(payload.Err.Error(), "use of closed network connection") {
				// connection closed
				r.closeClient(client)
				logger.Info("connection closed: " + client.RemoteAddr().String())
				return
			} else {
				// protocol err
				errReply := reply.MakeErrReply(payload.Err.Error())
				err := client.Write(errReply.ToBytes())
				if err != nil {
					r.closeClient(client)
					logger.Info("connection closed: " + client.RemoteAddr().String())
					return
				}
				continue
			}
		}
		if payload.Data == nil {
			logger.Error("empty payload")
			continue
		}
		reply, ok := payload.Data.(*reply.MultiBulkReply)
		if !ok {
			logger.Error("require multi bulk reply")
			continue
		}
		result := r.db.Exec(client, reply.Args)
		if result != nil {
			_ = client.Write(result.ToBytes())
		} else {
			_ = client.Write(unknownErrReplyBytes)
		}
	}
}

// Close 方法一定要保证线程安全，sync.Map线程安全
// Close stops handler
func (r *RespHandler) Close() error {
	logger.Info("handler shutting down...")
	r.closing.Set(true)
	// TODO: concurrent wait
	r.activeConn.Range(func(key, value any) bool {
		conn := key.(*connection.Connection)
		_ = conn.Close()
		return true
	})
	r.db.Close()
	return nil
}
